package com.xuecheng.search.service.impl;

import com.xuecheng.api.search.model.dto.CoursePubIndexDTO;
import com.xuecheng.api.search.model.qo.QueryCoursePubModel;
import com.xuecheng.common.domain.code.CommonErrorCode;
import com.xuecheng.common.domain.page.PageRequestParams;
import com.xuecheng.common.domain.page.PageVO;
import com.xuecheng.common.domain.response.RestResponse;
import com.xuecheng.common.exception.ExceptionCast;
import com.xuecheng.common.util.JsonUtil;
import com.xuecheng.common.util.StringUtil;
import com.xuecheng.search.common.constant.ContentSearchErrorCode;
import com.xuecheng.search.service.CoursePubSearchService;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MultiMatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;

/**
 *  课程搜索服务实现层(es原始Api实现)
 */
@Slf4j
@Service
public class CoursePubSearchServiceImpl implements CoursePubSearchService {

    @Value("${xuecheng.elasticsearch.course.index}")
    private String indexName;

    @Autowired
    private RestHighLevelClient client;

    /*
     * 主方法的分析：
     *   1.创建出SearchRequest
     *       1.1 SearchRequest 设置索引库的名称
     *       1.2 创建SearchSourceBuilder
     *           分页
     *           查询方式
     *           高亮
     *           排序（作业）
     *   2.获得响应数据SearchResponse
     *       通过Client方法Search方法来获得Response
     *   3.解析结果数据并封装PageVO中
     *       获得大Hits
     *       从大Hits获得总条数
     *       从大Hits获得小Htis数组
     *       遍历小Hits
     *       从小Hist中获得
     *           文档id
     *           文档的源数据_source
     *           文档的高亮
     * */
    public PageVO<CoursePubIndexDTO> queryCoursePubIndex(PageRequestParams params,
                                                         QueryCoursePubModel model) {
        PageVO<CoursePubIndexDTO> pageVO = null;


        try {
            // 1.创建出SearchRequest
            SearchRequest request = getSearchRequest(params,model);

            // 2.获得响应数据SearchResponse
            SearchResponse searchResponse = client.search(request, RequestOptions.DEFAULT);

            // 3.解析结果数据并封装PageVO中
            pageVO = parseResponse(searchResponse,params);
        } catch (IOException e) {
            log.error("课程搜索数据失败:{}", e.getMessage());
            ExceptionCast.cast(ContentSearchErrorCode.E_150001);
        }

        return pageVO;
    }

    /*
     * 解析结果数据并封装PageVO中
     * */
    private PageVO<CoursePubIndexDTO> parseResponse(SearchResponse searchResponse,PageRequestParams params) {

        // 1.获得响应数据的大hits
        SearchHits hits = searchResponse.getHits();

        // 2.查询的总条数
        long totalCount = hits.getTotalHits().value;

        // 3.获得小hits
        SearchHit[] hitsHits = hits.getHits();


        ArrayList<CoursePubIndexDTO> list = new ArrayList<>();

        // 4.遍历小hits封装数据到PageVO中
        for (SearchHit hitsHit : hitsHits) {

            // 获得文档的源数据内容
            String id = hitsHit.getId();

            String jsonString = hitsHit.getSourceAsString();

            CoursePubIndexDTO dto = JsonUtil.jsonToObject(jsonString, CoursePubIndexDTO.class);

            dto.setIndexId(new Long(id));

            // 获得高亮数据
            Map<String, HighlightField> highlightFields = hitsHit.getHighlightFields();

            HighlightField highlightField = highlightFields.get("name");

            if (!(ObjectUtils.isEmpty(highlightField))) {

                Text[] fragments = highlightField.getFragments();

                StringBuilder stringBuilder = new StringBuilder();

                for (Text fragment : fragments) {
                    stringBuilder.append(fragment);
                }

                String highLightName = stringBuilder.toString();

                dto.setName(highLightName);
            }

            list.add(dto);
        }

        PageVO<CoursePubIndexDTO> pageVO = new PageVO<>(list,totalCount,params.getPageNo(),params.getPageSize());

        return pageVO;
    }

    /*
        课程搜索功能需求：

            1.分页数据查询

            2.根据关键字进行查询

            3.根据课程分类和课程等级条件查询

            4.根据关键字进行查询后的内容要高亮显示。

    * 创建出SearchRequest
    *
    *
    *   1 SearchRequest 设置索引库的名称
        2 创建SearchSourceBuilder
            分页
            查询方式
            高亮
            排序（作业）
        3.构建查询方式
             Boolean
                must：
                    MultiMatchQuery
                filter：
                    TermQuery
        4.设置查询源数据对象
    *
    * */
    private SearchRequest getSearchRequest(PageRequestParams params,
                                           QueryCoursePubModel model) {

        // 0.判断分页数据
        if (params.getPageNo() < 1) {
            params.setPageNo(PageRequestParams.DEFAULT_PAGE_NUM);
        }
        if (params.getPageSize() < 1) {
            params.setPageSize(PageRequestParams.DEFAULT_PAGE_SIZE);
        }


        // 1.创建SearchRequest对象
        SearchRequest request = new SearchRequest(indexName);

        // 2.创建搜索源数据对象
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        // 2.1 设置分页数据

        Long pageNo = params.getPageNo();

        Integer pageSize = new Integer(params.getPageSize().toString());


        int from = (pageNo.intValue() - 1) * pageSize;

        sourceBuilder.from(from);
        sourceBuilder.size(pageSize);



        // 2.2 设置高亮数据
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.preTags("<font color='red'><b>");
        highlightBuilder.postTags("</b></font>");
        highlightBuilder.fields().add(new HighlightBuilder.Field("name"));

        sourceBuilder.highlighter(highlightBuilder);


        // 3.构建查询方式
        /*
        *   根据关键字进行查询
        *       1.对关键字内容需要进行分词
        *       2.需要对多个字段进行查询
        *
        *       MultiMatchQuery
        *           1.匹配查询--> 对查询的内容先分词后查询
        *           2.可以这是对个字段
        *
            根据课程分类和课程等级条件查询
               课程分类--> TermQuery
               课程等级--> TermQuery

             对于不分词的查询方式，我们可以使用过滤器来提高查询效率

            上面查询是多种查询方式来构成：BooleanQuery可以对多种查询方式来一同查询

        * */
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();



        String keyword = model.getKeyword();
        if (StringUtil.isNotBlank(keyword)) {
            MultiMatchQueryBuilder queryBuilder = QueryBuilders.multiMatchQuery(keyword, "name", "description")
                    .minimumShouldMatch("80%").field("name",3);
            boolQueryBuilder.must(queryBuilder);
        }


        String grade = model.getGrade();
        String mt = model.getMt();
        String st = model.getSt();


        if (StringUtil.isNotBlank(grade)) {
            TermQueryBuilder gradeTerm = QueryBuilders.termQuery("grade", grade);
            boolQueryBuilder.filter(gradeTerm);
        }

        if (StringUtil.isNotBlank(mt)) {
            TermQueryBuilder mtTerm = QueryBuilders.termQuery("mt", mt);
            boolQueryBuilder.filter(mtTerm);
        }

        if (StringUtil.isNotBlank(st)) {
            TermQueryBuilder stTerm = QueryBuilders.termQuery("st", st);
            boolQueryBuilder.filter(stTerm);
        }


        sourceBuilder.query(boolQueryBuilder);


        // 将查询源数据对象存放到Request
        request.source(sourceBuilder);


        return request;
    }


    //其他代码省略
    public CoursePubIndexDTO getPubIndexById(Long coursePubId) {

        // 1.判断关键数据
        if (ObjectUtils.isEmpty(coursePubId)) {
            ExceptionCast.cast(CommonErrorCode.E_100101);
        }

        // 2.创建请求对象
        GetRequest request = new GetRequest(indexName,coursePubId.toString());

        // 3.获得响应对象
        GetResponse getResponse = null;
        try {
            getResponse = client.get(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("查询课程搜索数据失败：{}",e.getMessage());
            ExceptionCast.cast(ContentSearchErrorCode.E_150001);
        }

        // 文档id值
        String id = getResponse.getId();

        String jsonString = getResponse.getSourceAsString();

        CoursePubIndexDTO coursePubIndexDTO = null;

        if (StringUtil.isBlank(jsonString)) {
            coursePubIndexDTO = new CoursePubIndexDTO();
        } else {
            coursePubIndexDTO = JsonUtil.jsonToObject(jsonString, CoursePubIndexDTO.class);
            coursePubIndexDTO.setIndexId(new Long(id));
        }

        return coursePubIndexDTO;

    }

    @Override
    public RestResponse<CoursePubIndexDTO> getCoursePubIndexById4s(Long coursePubId) {

        // 1.判断关键数据
        if (ObjectUtils.isEmpty(coursePubId)) {
            return RestResponse.validfail(CommonErrorCode.E_100101);
        }

        // 2.创建请求对象
        GetRequest request = new GetRequest(indexName,coursePubId.toString());

        // 3.获得响应对象
        GetResponse getResponse = null;
        try {
            getResponse = client.get(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("查询课程搜索数据失败：{}",e.getMessage());
            return RestResponse.validfail(ContentSearchErrorCode.E_150001);
        }

        // 文档id值
        String id = getResponse.getId();

        String jsonString = getResponse.getSourceAsString();

        CoursePubIndexDTO coursePubIndexDTO = null;

        if (StringUtil.isBlank(jsonString)) {
            return RestResponse.validfail(ContentSearchErrorCode.E_150002);
        } else {
            coursePubIndexDTO = JsonUtil.jsonToObject(jsonString, CoursePubIndexDTO.class);
            coursePubIndexDTO.setIndexId(new Long(id));

            return RestResponse.success(coursePubIndexDTO);
        }
    }


}